#pragma once
#include "./Object.hpp"
#include <fmt/format.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

class Renderer;

class Object3D : public Object {
public:
    Object3D() {}
    virtual ~Object3D() {}
    Object3D(Object3D &&other) : Object(std::move(other)), m_localPosition(std::move(other.m_localPosition)) {}
    Object3D &operator=(Object3D &&other) {
        Object::operator=(std::move(other));
        return *this;
    }

    FORCEINLINE auto position() const { return m_localPosition; }
    FORCEINLINE void setPosition(const glm::vec3 &p) {
        m_localPosition = p;
        m_needUpdateMatrix = true;
    }
    FORCEINLINE void setPosition(float x, float y, float z) { setPosition({x, y, z}); }
    FORCEINLINE auto scale() const { return m_localScale; }
    FORCEINLINE auto worldScale() const {
        return glm::vec3(glm::length(glm::vec3(m_worldMatrix[0])), glm::length(glm::vec3(m_worldMatrix[1])),
                         glm::length(glm::vec3(m_worldMatrix[2])));
    }
    FORCEINLINE void setScale(const glm::vec3 &s) {
        m_localScale = s;
        m_needUpdateMatrix = true;
    }
    FORCEINLINE void setScale(float x, float y, float z) { setScale({x, y, z}); }
    FORCEINLINE auto rotation() const { return m_localRotation; }
    FORCEINLINE void setRotation(const glm::vec3 &r) {
        m_localRotation = r;
        m_needUpdateMatrix = true;
    }
    FORCEINLINE void setRotation(float x, float y, float z) { setRotation({x, y, z}); }
    FORCEINLINE void setRotationDeg(const glm::vec3 &deg) { setRotation(glm::radians(deg)); }
    FORCEINLINE void setRotationDeg(float x, float y, float z) {
        setRotation({glm::radians(x), glm::radians(y), glm::radians(z)});
    }
    FORCEINLINE glm::mat4 worldMatrix() const { return m_worldMatrix; }
    void updateMatrix() {
        if (m_needUpdateMatrix) {
            m_localMatrix = glm::scale(glm::mat4(1.0f), m_localScale);
            auto rot = glm::rotate(glm::mat4(1.0f), m_localRotation.z, glm::vec3(0, 0, 1)) * // roll
                       glm::rotate(glm::mat4(1.0f), m_localRotation.x, glm::vec3(1, 0, 0)) * // pitch
                       glm::rotate(glm::mat4(1.0f), m_localRotation.y, glm::vec3(0, 1, 0));  // yaw
            m_localMatrix = glm::translate(glm::mat4(1.0f), m_localPosition) * rot * m_localMatrix;
            m_worldMatrix =
                isRoot() ? m_localMatrix : reinterpret_cast<Object3D *>(parent())->worldMatrix() * m_localMatrix;
            m_needUpdateRenderData = true;
            m_needUpdateMatrix = false;
        }
    }

public:
    FORCEINLINE bool visible() const { return m_visible; }
    FORCEINLINE void setVisible(bool v) { m_visible = v; }

protected:
    virtual void update(float ms) {
        updateMatrix();
        // onUpdate(ms);
        updateChildren(ms);
    }

    glm::vec3 m_localPosition{0.0f};
    glm::vec3 m_localScale{1.0f};
    glm::vec3 m_localRotation{0.0f}; // pitch, yaw, roll

    friend class Renderer;
    bool m_visible{true};
    bool m_needUpdateTree{true};
    char m_needUpdateMatrix{true}; // Preserved for Camera3D
    bool m_needUpdateRenderData{true};
    mutable glm::mat4 m_localMatrix{1.0f};
    mutable glm::mat4 m_worldMatrix{1.0f};
};